﻿namespace HuwmanCode.ServiceProcess

open System
open System.Diagnostics
open System.ComponentModel
open System.Configuration
open System.Configuration.Install
open System.ServiceProcess
open System.Runtime.InteropServices
open HuwmanCode
open HuwmanCode.Reflection
open HuwmanCode.Diagnostics
open HuwmanCode.Diagnostics.Logging
open HuwmanCode.Runtime.InteropServices

[<RunInstaller(true)>]
[<DesignerCategory("Default")>]
type public ServiceProjectInstallerBase() as this =
    inherit Installer()
    do
        let createProcessInstaller() =
            trace "Create process installer" {
                let serviceAccount = this.GetType().GetCustomAttributes<ServiceAccountAttribute> (false) |> Array.tryFind(fun e -> true)
                let account = serviceAccount |> Option.mapOrDefault2 (fun a -> a.Account) ServiceAccount.LocalService
                let userName = serviceAccount |> Option.mapOrDefault2 (fun a -> a.UserName) String.Empty

                return new ServiceProcessInstaller(Account=account, Username = userName) :> Installer }

        createProcessInstaller() :: Base.GetServiceInstallers() |> List.toArray |> base.Installers.AddRange             

    static let StartServices(services:WindowsServiceBase array) =
        let servicesToStart = trace "Get services to start" { return services |> Array.filter(fun s -> s.StartAfterInstall) }
        try
            for service in servicesToStart do
                trace "Starting services after install" { 
                    use sc = new ServiceController(service.ServiceName)
                    if sc.Status = ServiceControllerStatus.Stopped then 
                        sc.Start() 
                        return Some sc.Status
                    else return None } |> ignore
        with _ -> ()
    
    static let ConfigureServices(services:WindowsServiceBase array) =
        try
            let configureService scmHandle serviceName actions resetFailCountAfter rebootMessage =
                let command = sprintf "\"%s\" recover: %s" Process.Current.MainModule.FileName ("{" + sprintf "serviceName=\"%s\";" serviceName + "failCount=%1%}")
                use serviceHandle = OpenService(scmHandle, serviceName, SERVICE_ALL_ACCESS)
                use actionMem = actions |> Marshal.packUnmanaged
                use actionsMem = [|new ServiceFailureActions(command, actions.Length, actionMem.Handle, resetFailCountAfter, rebootMessage)|] |> Marshal.packUnmanaged
            
                let success = ChangeServiceConfig2(serviceHandle, ServiceConfig2InfoLevel.FailureActions, actionsMem.Handle)
                if success = false then failwith (sprintf "Failed to configure service %s!" serviceName)
        
            use scmHandle = OpenSCManager(null, null, SC_MANAGER_ALL_ACCESS);
            services 
            |> Array.filter(fun s -> s.RecoveryOptions.IsSome && s.RecoveryOptions.Value.Actions <> [||]) 
            |> Array.iter(fun s -> let v = s.RecoveryOptions.Value in configureService scmHandle s.ServiceName v.Actions v.ResetFailCountAfter v.RebootMessage)
        with e -> raise(new System.Configuration.ConfigurationErrorsException("Unable to configure services", e))

    override x.Commit(savedState) =
        trace "Install commit" {
            let services = Base.Services
            services |> ConfigureServices
            services |> StartServices }
